﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace Charting
{
    class Tube
    {
        const int divsPerQuater = 12;
        const double radius = 0.1;
        const double requestedCornerRadius = 0.05;

        Color color;
        Color translucentColor;
        SolidColorBrush brush;
        string tip;
        GeometryModel3D geometryModel;
        MeshGeometry3D mesh;

        internal Tube(Vector3D offset, double height, Color color, string tip)
        {
            this.tip = tip;

            this.geometryModel = new GeometryModel3D {
                Transform = new TranslateTransform3D(offset) };

            var materialGroup = new MaterialGroup();
            this.geometryModel.Material = materialGroup;
            this.geometryModel.BackMaterial = materialGroup;

            this.color = color;
            this.translucentColor = Color.FromArgb(128, color.R, color.G, color.B);
            this.brush = new SolidColorBrush(this.translucentColor);

            var diffuseMaterial = new DiffuseMaterial(this.brush);
            materialGroup.Children.Add(diffuseMaterial);

            var specularMaterial = new SpecularMaterial
            {
                Brush = new SolidColorBrush(Color.FromArgb(192, 255, 255, 255)),
                SpecularPower = 50
            };
            materialGroup.Children.Add(specularMaterial);

            this.mesh = new MeshGeometry3D();
            this.geometryModel.Geometry = this.mesh;

            this.AddPositionsAndNormals(height);
            this.AddIndices();
        }

        internal Model3D Model { get { return this.geometryModel; } }

        internal string Tip { get { return this.tip; } }

        internal bool IsHovered
        {
            get { return this.brush.Color == this.color; }
            set { this.brush.Color = value ? this.color : this.translucentColor; }
        }

        void AddPositionsAndNormals(double height)
        {
            double cornerRadius = Math.Min(requestedCornerRadius, height);

            for (int i = 0; i < 4 * divsPerQuater; i++)
            {
                double a = i * Math.PI / 2 / divsPerQuater;
                double cosA = Math.Cos(a);
                double sinA = Math.Sin(a);

                this.mesh.Positions.Add(new Point3D(radius * cosA, 0, radius * -sinA));
                this.mesh.Normals.Add(new Vector3D(cosA, 0, -sinA));

                for (int j = 0; j < divsPerQuater + 1; j++)
                {
                    double b = j * Math.PI / 2 / divsPerQuater;
                    double cosB = Math.Cos(b);
                    double sinB = Math.Sin(b);

                    double r = radius - cornerRadius + cornerRadius * cosB;
                    double x = r * cosA;
                    double y = height - cornerRadius + cornerRadius * sinB;
                    double z = r * -sinA;

                    this.mesh.Positions.Add(new Point3D(x, y, z));
                    this.mesh.Normals.Add(new Vector3D(cosB * cosA, sinB, cosB * -sinA));
                }
            }

            this.mesh.Positions.Add(new Point3D(0, height, 0));
            this.mesh.Normals.Add(new Vector3D(0, 1, 0));
        }

        void AddIndices()
        {
            for (int i = 0; i < 4 * divsPerQuater + 1; i++)
            {
                for (int j = 0; j < divsPerQuater + 1; j++)
                {
                    int a = i * (divsPerQuater + 2) + j;
                    int b = ((i + 1) % (4 * divsPerQuater)) * (divsPerQuater + 2) + j;
                    int c = i * (divsPerQuater + 2) + j + 1;
                    int d = ((i + 1) % (4 * divsPerQuater)) * (divsPerQuater + 2) + j + 1;

                    new List<int> { a, b, c, c, b, d }.ForEach(this.mesh.TriangleIndices.Add);

                    if (j == divsPerQuater)
                    {
                        this.mesh.TriangleIndices.Add(c);
                        this.mesh.TriangleIndices.Add(d);
                        this.mesh.TriangleIndices.Add(this.mesh.Positions.Count - 1);
                    }
                }
            }
        }
    }
}
